package net;

import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Hashtable;

import model.Player;
import pack.Engine;
import pack.World;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IConnection;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.Server;

import util.Logger;


public final class ChannelListener implements IConnectHandler, IDataHandler, IDisconnectHandler {
    private Hashtable<String, Integer> connectionCounts = new Hashtable<String, Integer>();
    private Server server;
    private World world;
    private String host;

    public ChannelListener(World world, String host) throws IOException {
        server = new Server(host, 43594, this);
        server.setFlushmode(IConnection.FlushMode.ASYNC);
        server.setIdleTimeoutMillis(60000);
        Engine.newThread(server);
        this.world = world;
        this.host = host;
    }

    public void close() {
        server.close();
    }

    public String host() {
        return host;
    }

    public static void write(Player p) {
        if (p.out().offset() == 0) {
            return;
        }
        try {
            if (!p.connection().isOpen()) {
                throw new IOException("Channel is closed");
            }
            int count = p.connection().write(p.out().copy());
            if (count == -1) {
                throw new IOException("Channel closed by remote host");
            } else if (count != p.out().offset()) {
                throw new IOException("Channel unable to write all bytes");
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        p.out().setOffset(0);
    }

    @Override
    public boolean onConnect(INonBlockingConnection connection) throws IOException {
        try {
            String hostAddress = connection.getRemoteAddress().getHostAddress();
            Logger.println("Connection recieved on world " + world.id() + ": " + hostAddress);
            if (connectionCounts.containsKey(hostAddress)) {
                connectionCounts.put(hostAddress, connectionCounts.get(hostAddress) + 1);
            } else {
                connectionCounts.put(hostAddress, 1);
            }
            Player p = new Player(connection, world);
            connection.setAttachment(p);
            synchronized (world.LOCK) {
                world.players().add(p);
            }
            if (connectionCounts.get(hostAddress) > 5) {
                connection.close();
            }
        } catch (Exception e) {
            connection.close();
        }
        return true;
    }

    @Override
    public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
        if (!connection.isOpen()) {
            throw new IOException("Channel is closed");
        }
        int available = connection.available();
        if (available == -1) {
            throw new IOException("Channel closed by remote host");
        } else if (available == 0) {
            return true;
        } else if (available > 5000) {
            throw new IOException("Channel is flooding");
        }
        Player p = (Player) connection.getAttachment();
        if (p == null) {
            connection.close();
            return true;
        }
        ByteBuffer in = ByteBuffer.allocate(available);
        int count = connection.read(in);
        if (count == -1) {
            throw new IOException("Channel closed by remote host");
        } else if (count == 0) {
            return true;
        } else {
            synchronized (p.in()) {
                p.in().addBytes(in.array(), 0, count);
            }
        }
        return true;
    }

    @Override
    public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
        Player p = (Player) connection.getAttachment();
        if (p != null) {
            synchronized (world.LOCK) {
                world.players().remove(p);
                p.setIsVisible(false);
            }
            String hostAddress = p.hostAddress();
            Logger.println("Connection closed on world " + world.id() + ": " + hostAddress);
            if (connectionCounts.get(hostAddress) > 1) {
                connectionCounts.put(hostAddress, connectionCounts.get(hostAddress) - 1);
            } else {
                connectionCounts.remove(hostAddress);
            }
        }
        return true;
    }
}